﻿using System;

namespace ArtSpec
{
    /// <summary>
    /// Inherit your specifications from this class
    /// </summary>
    public abstract class Spec
    {
        private SpecifyContext currentContext;
        private readonly SpecifyContext mainContext;

        /// <summary>
        /// Notifies about specification validation result. Used to monitor validation progress.
        /// </summary>
        public event Action<ISpec> OnResult;

        /// <summary>
        /// Override this method to specify behavior
        /// </summary>
        protected abstract void Specify();

        /// <summary>
        /// Validates specification
        /// </summary>
        public void Validate()
        {
            Specify();
            mainContext.Verify();
        }

        protected Spec()
        {
            System.Reflection.MemberInfo info = GetType();
            var attributes = info.GetCustomAttributes(true);
            string description = null;
            foreach (var attribute in attributes)
            {
                if (!(attribute is SpecAttribute)) continue;
                var specAttribute = (SpecAttribute)attribute;
                description = specAttribute.Description;
            }
            currentContext = mainContext = new SpecifyContext(null, description ?? GetType().Name.Replace("Test", "").Replace("Spec", ""));
            MonitorResult(mainContext);
        }

        /// <summary>
        /// Defines specification context. Should be used to setup an actual state.
        /// </summary>
        protected ISpecify Context(string description)
        {
            var context = new SpecifyContext(currentContext, currentContext.Description + " " + description);
            context.OnContextChange += current => currentContext = current;
            MonitorResult(context);

            currentContext.Add(context);
            return context;
        }

        /// <summary>
        /// Defines generic error expectation
        /// </summary>
        protected static void Error(Action action)
        {
            Error<Exception>(action);
        }

        /// <summary>
        /// Defines custom error expectation
        /// </summary>
        /// <typeparam name="ExceptionType">expected error type</typeparam>
        protected static void Error<ExceptionType>(Action action)
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                var actual = e.GetType();
                var expected = typeof(ExceptionType);
                if (actual == expected || actual.IsSubclassOf(expected))
                {
                    return;
                }
                throw;
            }
            throw new Exception("Expected exception not raised");
        }

        private void MonitorResult(ISpecifyContext context)
        {
            context.OnResult += sender => Fire.Event(OnResult, sender);
        }

        /// <summary>
        /// Defines specification stub
        /// </summary>
        /// <param name="description">Describe what you expect</param>
        protected void It(string description)
        {
            It(description, null);
        }

        /// <summary>
        /// Defines specification
        /// </summary>
        protected void It(string description, Action action)
        {
            var spec = new SpecifyIt(currentContext.Description + " " + description, action);
            currentContext.Add(spec);
        }

        /// <summary>
        /// Defines action to be executed before each specification
        /// </summary>
        protected virtual void BeforeEach(Action action)
        {
            currentContext.OnBeforeEach += action;
        }

        /// <summary>
        /// Defines action to be executed after each specification
        /// </summary>
        protected virtual void AfterEach(Action action)
        {
            currentContext.OnAfterEach += action;
        }

        /// <summary>
        /// Defines action to be executed before each specification
        /// </summary>
        protected virtual void BeforeAll(Action action)
        {
            currentContext.BeforeAll = action;
        }

        /// <summary>
        /// Defines action to be executed after each specification
        /// </summary>
        protected virtual void AfterAll(Action action)
        {
            currentContext.AfterAll = action;
        }
    }
}